#! /usr/bin/python
# coding=utf-8

import inspect
#import itertools
import optparse
import os
import random
import re
import readline
import sys
import time
#import traceback

#raise SyntaxError("A descriptive error message", (filename, linenum, colnum, source_line))


global_info = {}


parser = optparse.OptionParser(usage="usage: %prog [options...] [files..]", version="%prog 1.0")

parser.set_defaults(loose_parens=False)

parser.add_option("-L", "--allow-omit-parens", action="store_true", dest="loose_parens",
    help="Allows for omitting parentheses at the end of an expression, like so: (foo (bar (qux corge")

parser.add_option("-b", "--backwards-compat", action="store_true", dest="backwards_compat",
    help="Tries to be more compatible with Arc 3.1, such as allowing (o a 1) for optional arguments")

parser.add_option("--run-tests", action="store_true", dest="run_tests",
    help="Runs all the unit tests, to verify the interpreter's correctness")

parser.add_option("--repl", action="store_true", dest="repl",
    help="Normally, the REPL is only run when no other files are loaded. This option causes the REPL to always run.")


(option, args) = parser.parse_args()


reader_syntax = "(%s)" % "|".join([r"#\\",
                                    "#!",
                                    ";",
                                    '"',
                                    "\\|",
                                    "\\[",
                                    "\\]",
                                    "\\(",
                                    "\\)",
                                    "\\s",
                                    "\\\\"])


def mirror(table):
    for key in table.keys():
        table[table[key]] = key

    return table


def warning(message):
    print "Warning: " + message + "\n"


def pair(item):
    i = iter(item)

    try:
        while True:
            a = nil

            a = i.next()
            b = i.next()

            yield a, b

    except StopIteration:
        if a is nil:
            return
        else:
            yield a, None


class ArcError(Exception):
    def __init__(self, value):
        self.message = value

    def __str__(self):
        return self.message


class ArgumentError(TypeError):
    def __init__(self, value):
        self.message = value

    def __str__(self):
        return self.message


def print_():
    mappings = {
        "fexpr": "fexpr",
        "mac": "macro",
        "fn": "function",
    }

    def f(x):
        if x.type in mappings:
            if x.__name__:
                return "<%s %s>" % (mappings[x.type], x.__name__)
            else:
                return "<%s>" % mappings[x.type]
        else:
            return repr(x.rep)

    return f

print_ = print_()


def attr(x, m):
    try:
        if m == "call":
            return x.__call__
        elif m == "car":
            return x.car
        elif m == "cdr":
            return x.cdr
        elif m == "set":
            return x.__setitem__
        elif m == "keys":
            return x._keys
        elif m == "rep":
            return x.rep
        else:
            raise AttributeError

    except AttributeError:
        raise AttributeError("expression %s has no attribute %s" % (x, m))


class base(object):
    def __ne__(self, other):
        return not self == other

    def __le__(self, other):
        return self < other or self == other

    def __ge__(self, other):
        return self > other or self == other

    def __repr__(self):
        return print_(self)


class num_(base):
    def __init__(self, value):
        self.type = symbol("num")
        self.rep = self

        self.value = value

    def __repr__(self):
        return str(self.value)

    def __float__(self):
        return float(self.value)

    def __int__(self):
        return int(self.value)

    def __add__(self, other):
        return self.value + other

    __radd__ = __add__

    def __iadd__(self, other):
        self.value += other
        return self

    def __isub__(self, other):
        self.value -= other
        return self

    def __eq__(self, other):
        return self.value == other

    def __lt__(self, other):
        return self.value < other

    def __gt__(self, other):
        return self.value > other


class int_(num_):
    def __init__(self, value):
        self.type = symbol("int")
        self.rep = self

        self.value = value


class annotate(base):
    def __init__(self, type, value):
        self.type = type
        self.rep = value

    @property
    def __radd__(self):
        return self.rep.__radd__

    @property
    def __iter__(self):
        return self.rep.__iter__

    @property
    def __getitem__(self):
        return self.rep.__getitem__

    @property
    def __setitem__(self):
        return self.rep.__setitem__

    @property
    def __delitem__(self):
        return self.rep.__delitem__

    @property
    def __call__(self):
        return self.rep.__call__

    @property
    def __name__(self):
        return self.rep.__name__

    def __getattr__(self, n):
        return getattr(self.rep, n)


class yield_(annotate):
    def __init__(self, value):
        self.rep = value
        self.cont = True


class Symbol(base):
    def __init__(self, value):
        self.rep = self
        self.value = str(value)
        self.special = False

        if re.search(reader_syntax, self.value):
            self.special = True

    def __repr__(self):
        value = self.value

        if self.special:
            value = "|" + value + "|"

        return value

    def __eq__(self, other):
        return self.value == other

    def __hash__(self):
        return hash(self.value)


symbol_cache = {}

def symbol(value):
    if not value in symbol_cache:
        symbol_cache[value] = Symbol(value)
        symbol_cache[value].type = symbol("sym")

    return symbol_cache[value]


class uniq(Symbol):
    def __init__(self, value=symbol("gs")):
        count = current_env["gensym-counter*"]

        self.type = symbol("sym")
        self.rep = self

        self.value = coerce(value, "sym").value + str(count)

        self.special = False

        if re.search(reader_syntax, self.value):
            self.special = True

        try:
            count += 1
        except TypeError:
            raise TypeError("gensym-counter* must be a number")

    def __eq__(self, other):
        return self is other


char_write_table = {}
char_read_table = {}

class char(base):
    def constant(f, t):
        char_write_table[f] = t
        char_read_table[t] = f

    constant(" ",  "space")
    constant("\n", "newline")
    constant("\t", "tab")
    constant("\r", "return")
    constant("\b", "backspace")

    def __init__(self, value):
        value = char_read_table.get(value, value)

        if len(value) > 1:
            raise SyntaxError("char must have a length of 1")

        self.type = symbol("char")
        self.rep = self

        self.value = value

    def __repr__(self):
        return r"#\%s" % char_write_table.get(self.value, self.value)

    def __str__(self):
        return self.value

    def __len__(self):
        if self.value == "":
            return 0
        else:
            return 1

    def __eq__(self, other):
        return self.value == other

    def __hash__(self):
        return hash(self.value)


class nil(base):
    def __init__(self):
        self.type = symbol("sym")
        self.rep = self
        self.car = self
        self.cdr = self

    def __eq__(self, other):
        return ((self is other) or
                (other is empty) or
                (other == "nil"))

    def __hash__(self):
        return hash("nil")

    def __repr__(self):
        return "nil"

    def __iter__(self):
        return iter("")

    def __len__(self):
        return 0


class empty(nil):
    def __init__(self):
        self.type = symbol("sym")
        self.rep = self
        self.car = nil
        self.cdr = nil

    def __repr__(self):
        return "()"


nil = nil()
empty = empty()

t = symbol("t")


class fn(base):
    def __init__(self, names, *body):
        self.env = environment(current_env)

        self.opts = []
        self.rest = None
#        self.body = [macex(x) for x in body]
        self.body = body

        self.iter = iter(self.body)

        self.lower = 0
        self.upper = 0

        self.type = symbol("fn")
        self.rep = self

        self.__name__ = None

        while True:
            if names == nil:
                break

            if names.type == "cons":
                x = names.car

                if x.type == "cons":
                    self.upper += 1

                    if option.backwards_compat:
                        if x.car == "o":
                            x = x.cdr

                            info = {
                                "type": "optional",
                                "phase": "every",
                                "name": x.car,
                                "value": x.cdr.car
                            }

                            self.env.set(info["name"], nil)

                            self.opts.append(info)

                        else:
                            self.opts.append({
                                "type": "destruct",
                                "name": x
                            })

                    elif x.car == "=":
                        y = x.cdr

                        if y.cdr == nil:
                            raise SyntaxError("expected %s or '%s but got %s" % (y.car, x, x))

                        info = {
                            "type": "optional",
                            "phase": "once",
                            "name": y.car,
                            "value": eval(y.cdr.car, self.env)
                        }

                        self.env.set(info["name"], info["value"])

                        self.opts.append(info)

                    elif x.car == "quote":
                        self.opts.append({
                            "type": "destruct",
                            "name": x.cdr.car
                        })

                    else:
                        y = x.cdr
                        if y != nil and y.cdr != nil and y.car.type == "sym":
                            raise SyntaxError("expected %s or '%s but got %s" % (cons(symbol("="), y), x, x))
                        else:
                            raise SyntaxError("expected '%s but got %s" % (x, x))

                else:
                    self.upper += 1

                    self.opts.append({
                        "type": "optional",
                        "phase": "once",
                        "name": x,
                        "value": nil
                    })

                    self.env.set(x, nil)

                y = names.cdr

                if y.type == "sym" and y != nil:
                    rest = y

                names = names.cdr
            else:
                self.rest = names
                break


    def destruct(self, x, y):
        try:
            it = iter(y)
        except TypeError as e:
            raise ArgumentError("can't destructure %s into %s" % (y, x))

        val = nil

        for name in x:
            if y.type != "table":
                val = next(it, nil)

            if name == nil:
                continue

            if name.type == "cons":
                if option.backwards_compat and name.car == "o":
                    if val == nil:
                        val = name.cdr.cdr.car

                    name = name.cdr.car

                    self.env.set(name, val)
                else:
                    self.destruct(name, val)
            else:
                if y.type == "table":
                    self.env.set(name, y[name])
                else:
                    self.env.set(name, val)


    def checkargs(self, args):
        passed = len(args)

        if (self.rest is None and passed > self.upper) or passed < self.lower:
#            if rest is None:
            if self.lower == self.upper:
                if self.lower == 1:
                    raise ArgumentError("expected exactly %s argument (%s given)" % (self.lower, passed))
                else:
                    raise ArgumentError("expected exactly %s arguments (%s given)" % (self.upper, passed))
            else:
                raise ArgumentError("expected %s to %s arguments (%s given)" % (self.lower, self.upper, passed))
#            else:
#                raise ArgumentError("expected %s or more arguments (%s given)" % (lower, passed))


    def setargs(self, args):
        args = iter(args)

        for item in self.opts:
            value = next(args, nil)

            if item["type"] == "optional":
                if value == nil:
                    if item["phase"] == "once":
                        value = item["value"]

                    elif item["phase"] == "every":
                        value = eval(item["value"], self.env)

                self.env.set(item["name"], value)

            elif item["type"] == "destruct":
                self.destruct(item["name"], value)

        if self.rest is not None:
            self.env.set(self.rest, mapcons(args))


    def call(self, *args):
        self.setargs(args)

#*      Shorter, but doesn't work with `yield`:
#
#        for item in self.body:
#            val = eval(item, self.env)
#
#        return val

        val = None

        try:
            while True:
                if val is None:
                    try:
                        val = self.iter.next()
                    except StopIteration:
                        self.iter = iter(self.body)
                        val = nil

                else:
                    val = self.iter.next()

                val = eval(val, self.env)

                if getattr(val, "cont", None):
                    return val.rep

        except StopIteration:
            pass

        return val


    def __call__(self, *args):
        self.checkargs(args)

        return self.call(*args)


class fnwrap(fn):
    def __init__(self, call, name=None):
        self.type = symbol("fn")
        self.rep = self
        self.call = call

        if name is None:
            name = call.__name__

        self.__name__ = name

        #* Python makes it possible, but difficult, to inspect a function's argument list

        try:
            args = inspect.getargspec(call)
            self.upper = len(args.args)
        except TypeError:
            try:
                args = inspect.getargspec(call.__call__)
            except TypeError:
                args = inspect.getargspec(call.__init__)

            self.upper = len(args.args) - 1

        self.lower = self.upper

        try:
            self.lower -= len(args.defaults)
        except TypeError:
            pass

        self.rest = args.varargs


class fexpr(fnwrap):
    def __init__(self, *args):
        fnwrap.__init__(self, *args)
        self.type = symbol("fexpr")


class macro(fnwrap):
    def __init__(self, *args):
        fnwrap.__init__(self, *args)
        self.type = symbol("mac")


def wrap(name):
    def f(fn):
        fn.type = symbol("fn")
        fn.__name__ = name
        return fn

    return f


class table(base):
    def __init__(self, value=None):
        self.type = symbol("table")
        self.rep = self

        if value is None:
            value = {}

        self.value = value

    def __repr__(self):
        return repr(self.value)

    @wrap("call")
    def __getitem__(self, k, v=nil):
        try:
            return self.value[k]
        except KeyError:
            return v

    @wrap("set")
    def __setitem__(self, k, v):
        self.value[k] = v

    @wrap("keys")
    def _keys(self):
        return mapcons(self.value.keys())

    def __delitem__(self, k):
        del self.value[k]

    __call__ = __getitem__


class output(base):
    def __init__(self, it):
        self.type = symbol("fn")
        self.rep = self

        self.__name__ = it
        self.stream = it

    def close(self):
        return getattr(sys, self.stream).close()

    def __call__(self, x, out=nil):
        if out == nil:
            out = current_env["stdout"]

        if x.type == "string":
            v = x.string()
        elif x.type == "char":
            v = x.value
        else:
            v = repr(x)

        getattr(sys, self.stream).write(v)
        return x


class input(output):
    def __init__(self, it):
        output.__init__(self, it)
        self.stream = iter(self)
        self.lineno = 0

    def __call__(self):
        return next(self.stream, nil)

    def __iter__(self):
        if not self.stream.isatty():
            for item in getattr(sys, self.stream):
                for x in item:
                    print x
                    yield char(x)


class stream(input):
    def __init__(self, it, path=None):
        output.__init__(self, it)
        self.__name__ = None
        self.path = path
        self.lineno = 0

    def __iter__(self):
        return self

    def next(self):
        global current_file
        global current_lineno

        current_file = self.path

        x = self.stream.next()

        if x == "\n":
            self.lineno += 1
            current_lineno = self.lineno

        return x

    def close(self):
        self.stream = iter("")


stdin = input("stdin")
stdout = output("stdout")
stderr = output("stderr")


def disp(x, out=nil):
    if out == nil:
        out = current_env["stdout"]

#        coerce(out, "fn")(x)
    out(x)
    return nil


class cons(base):
    def __init__(self, car, cdr):
        self.type = symbol("cons")
        self.rep = self
        self.car = car
        self.cdr = cdr

    def __iter__(self):
        item = self

        while True:
            if item == nil:
                return

            yield item.car
            item = item.cdr


    def __len__(self):
        i = 0

        for item in self:
            i += 1

        return i


    def __getitem__(self, index):
        i = index

        try:
            if i < 0:
                raise IndexError

            it = iter(self)

            try:
                if int(i) != i:
                    raise ValueError

            except (TypeError, ValueError):
                for item in it:
                    if item.type == "cons":
                        if item.car == index:
                            return item.cdr.car

                    else:
                        raise TypeError("%s is not an association list" % self)

                return nil

            try:
                while i >= 0:
                    item = it.next()
                    i -= 1
            except StopIteration:
                raise IndexError

        except IndexError:
            raise IndexError("index must be >= 0 and <= %s" % (len(self) - 1))

        return item


    def __setitem__(self, index, value):
        i = index

        try:
            if i < 0:
                raise IndexError
            item = self

            try:
                if int(i) != i:
                    raise ValueError

            except (TypeError, ValueError):
                while True:
                    if item.cdr == nil:
                        value = cons(index, cons(value, nil))
                        item.cdr = cons(value, nil)
                        return value

                    if item.car.type == "cons":
                        pass
                    else:
                        raise TypeError("%s is not an association list" % self)

                    item = item.cdr

            while True:
                if item == nil:
                    raise IndexError

                if i == 0:
                    item.car = value
                    return value

                i -= 1

                item = item.cdr

        except IndexError:
            raise IndexError("index must be >= 0 and <= %s" % (len(self) - 1))


    def __eq__(self, other):
        if isinstance(other, cons):
            return repr(self) == repr(other)

        return False


    def __hash__(self):
        return hash(repr(self))


    def flatten(self):
        value = []

        for item in self:
            if item.type == "cons":
                value += item.flatten()
            elif item.type == "string":
                value.append(item.string())
            else:
                value.append(str(item.rep))

        return value


    def __repr__(self):
        result = []

        item = self

        while True:
            if item == nil:
                return "(%s)" % " ".join(result)

            if item.type == "cons":
                result.append(repr(item.car))

                item = item.cdr
            else:
                return "(%s . %s)" % (" ".join(result), repr(item))


def mapcons(xs, f=lambda x: x):
    xs = iter(xs)

    try:
        result = item = cons(f(xs.next()), nil)
    except StopIteration:
        return nil

    for x in xs:
        item.cdr = cons(f(x), nil)
        item = item.cdr

    return result


class string(cons):
    def __init__(self, value=None):
        if value is not None:
            result = mapcons(str(value), lambda x: char(x))
        else:
            result = nil

        self.type = symbol("string")
        self.rep = self

        self.car = result.car
        self.cdr = result.cdr


    def string(self, f=lambda x: str(x)):
        return "".join([f(x) for x in self if x != nil])


    def empty(self):
        return self.car == nil and self.cdr == nil


    def __len__(self):
        if self.empty():
            return 0
        else:
            return cons.__len__(self)


    mappings = mirror({
        "\b": r"\b",
        "\n": r"\n",
        "\r": r"\r",
        "\t": r"\t",
        "\\": r"\\",
        "\"": r'\"',
    })

    def __repr__(self):
        def f(x):
#            if x == '"':
#                return r'\"'
#            else:
            x = x.value
            return self.mappings.get(x, x)

        return '"%s"' % self.string(f)


class environment(base):
    def __init__(self, outer=None, name=None):
        self.type = symbol("table")
        self.rep = self

        self.outer = outer
        self.value = {}

        self.__name__ = name
#
#
#    def find(self, name):
#        try:
#            if name in self.value or self.outer is None:
#                return self.value
#
#            else:
#                return self.outer.find(name)
#
#        except AttributeError:
#            return self.outer #.rep


    def set(self, name, value):
        self.value[name] = value


    def __getitem__(self, name):
        try:
            return self.value[name]
        except KeyError:
            if self.outer is None:
                raise
            else:
                return self.outer[name]


    def __setitem__(self, name, value):
        if name in self.value or self.outer is None:
            self.value[name] = value
        else:
            self.outer[name] = value


    def __getattr__(self, name):
        return getattr(self.value, name)


    def __repr__(self):
        if self.__name__:
            return "<namespace %s>" % (self.__name__)
        else:
            return "<namespace>"


class namespace(environment):
    def __setitem__(self, name, value):
        self.set(name, value)


class default_namespace(namespace):
    def __setitem__(self, name, value):
        safe_env.value[name] = value
#
#
#    def __getitem__(self, name):
#        global current_module
#
#        if current_module is self:
#            return environment.__getitem__(self, name)
#        else:
#            return current_module[name]


safe_env = default_namespace(name="__safe")
global_env = current_env = current_module = default_namespace(safe_env, name="__built-ins*")



def q_(n, *args):
    if isinstance(n, str):
        n = symbol(n)

    value = result = cons(n, nil)

    for item in args:
        if result is None:
            value = result = cons()

        if isinstance(item, tuple):
            value.cdr = cons(q_(*item), nil)

        else:
            if isinstance(item, str):
                item = symbol(item)

            value.cdr = cons(item, nil)

        value = value.cdr

    return result


def rule(n, t, f):
    return mapcons([n, table({ symbol(t): fnwrap(f) })])


rules = [
    rule(char(":"),    "infix",  lambda l, r: q_("compose", l, r)),
    rule(char("~"),    "prefix", lambda r:    q_("complement", r)),
    rule(char("."),    "infix",  lambda l, r: q_(l, r)),
    rule(char("&"),    "infix",  lambda l, r: q_("andf", l, r)),
    rule(char("'"),    "prefix", lambda r:    q_("quote", r)),
    rule(char("`"),    "prefix", lambda r:    q_("quasiquote", r)),
    rule(string("<-"), "infix",  lambda l, r: q_("attr", l, ("quote", r))),
    rule(string(",@"), "prefix", lambda r:    q_("unquote-splicing", r)),
    rule(char(","),    "prefix", lambda r:    q_("unquote", r)),
]

#  (add-ssyntax-rules
#    (#\:  (l r) (list l (list r)))
#    (#\~  (r)   (list 'complement r))
#    (#\.  (l r) (list (list l r)))
#    (#\!  (l r) (list (list l (list 'quote r))))
#    (#\&  (l r) (list 'andf l r))
#    (#\'  (r)   (list 'quote r))
#    (#\`  (r)   (list 'quasiquote r))
#    ("<-" (l r) (list 'attr l (list 'quote r)))
#    (",@" (r)   (list 'unquote-splicing r))
#    (#\,  (r)   (list 'unquote r)))


if option.backwards_compat:
    rules.append(rule(char("!"), "infix", lambda l, r: q_(l, ("quote", r))))


global_env["ssyntax-rules*"] = mapcons(rules)

ssyntax_rules = {}

def ssyntax_update():
    global ssyntax_string

    def f(item):
        x = item.car

        if x.type == "string":
            x = x.string()
        else:
            x = str(x)

        ssyntax_rules[x] = item.cdr.car

        return re.escape(x)

    ssyntax_string = "(" + "|".join(map(f, global_env["ssyntax-rules*"])) + ")"

ssyntax_update()


def isliteral():
    types = set(["char", "string", "fn", "mac", "fexpr", "int", "num"])

    def f(expr):
        return expr is empty or expr.type in types

    return f

isliteral = isliteral()


def name(n, fn):
    try:
        fn.__name__ = n
    except AttributeError:
        pass

    return fn


def set_name():
    types = set(["fn", "mac", "table"])

    def f(n, v):
        if v.type in types:
            name(str(n), v.rep)

        return v

    return f

set_name = set_name()


def if_(*args):
    env = current_env

    for x, y in pair(args):
        x = eval(x, env)

        if x != nil:
            if y is None:
                return x
            else:
                return eval(y, env)

    return nil



#(assign quasiquote (annotate 'mac
#                     (fn (x)
#                       (if (is (type x) 'cons) (if (is (car x) 'unquote)          (car (cdr x))
#                                                   (is (car x) 'unquote-splicing) (err ",@ cannot be used immediately after `")
#                                                   (is (car x) 'quasiquote)       (cons 'quasiquote (cons (quasiquote (car (cdr x))) nil))
#                                                      (cons ))
#                                               (cons 'quote (cons x nil))))))
def quasiquote(n, env):
    def f(x):
        if x.type == "cons":
            if x.car == "unquote":
                return cons(eval(x.cdr.car, env), nil)

            elif x.car == "unquote-splicing":
                x = eval(x.cdr.car, env)

                if x == nil or x.type == "cons":
                    return x
                else:
                    raise TypeError("%s must be a list" % x)

            elif x.car == "quasiquote":
                return f(x)

            else:
                return cons(quasiquote(x, env), nil)
        else:
            return cons(x, nil)

    if n.type == "cons":
        if n.car == "unquote":
            return eval(n.cdr.car, env)

        elif n.car == "unquote-splicing":
            raise SyntaxError(",@ cannot be used immediately after `")

        result = item = f(n.car)

        for x in n.cdr:
            item.cdr = f(x)

            x = item

            while True:
                if x.cdr == nil:
                    item = x
                    break

                x = x.cdr

        return result

    else:
        return n
#
#
#def quasiquote_list(x):
#    if x.type == "cons":
#        if x.car == "unquote":
#            return q_("quote", x.cdr.car)
#        elif x.car == "unquote-splicing":
#            return x.cdr.car
#        elif x.car == "quasiquote":
#            return quasiquote_list(quasiquote(x.cdr.car))
#        else:
#            return q_("quote", x)
#    else:
#        return q_("quote", (x,))
#
#
#def quasiquote(x):
#    if x.type == "cons":
#        if x.car == "unquote":
#            return x.cdr.car
#        elif x.car == "unquote-splicing":
#            raise SyntaxError(",@ cannot be used immediately after `")
#        elif x.car == "quasiquote":
#            return quasiquote(quasiquote(x.cdr.car))
#        else:
#            return q_("quote")
#    else:
#        return q_("quote", x)


def assign(n, v=nil):
    env = current_env

    if n.type != "sym":
        raise TypeError("first argument to `assign` must be a symbol: %s" % n)

    v = eval(v, env)

    if n == "nil" and v != nil:
        warning("hey, you! it's a bad idea to rebind nil; you could break everything! use (assign nil '()) to fix the mess you probably made")

    set_name(n, v)

    env[n] = v
    return v


def quote(x):
    if x is empty:
        return nil
    else:
        return x
#
#
#def flat1(args):
#    result = list(args)
#
#    last = result[-1]
#
#    if last == nil:
#        del result[-1]
#    else:
#        result[-1:] = last
#
#    return [cons(symbol("quote"), cons(x, nil)) for x in result]


def flat1(args):
    vals = []

    for x in args:
        if x.type == "cons":
            for x in x:
                vals.append(q_("quote", x))
        else:
            vals.append(q_("quote", x))

    return vals


stack = []


def apply(proc, expr):
    env = current_env

    if proc.type == "fn":
        args = [eval(x, env) for x in expr]

#        stack.append("   fn: %s\n             %s" % (str(whole), "\n             ".join("%s" % x for x in args)))
#        stack.append("%s %s\n      %s" % (proc, env.value.keys(), "\n      ".join(repr(x) for x in args)))
        stack.append("%s\n      %s" % (proc, "\n      ".join(repr(x) for x in args)))

        return proc(*args)

    elif proc.type == "mac":
        stack.append("%s\n      %s" % (proc, "\n      ".join(repr(x) for x in expr)))

        return eval(proc(*expr), env)

    elif proc.type == "fexpr":
        stack.append("%s\n      %s" % (proc, "\n      ".join(repr(x) for x in expr)))

        return proc(*expr)

    else:
#        try:
        proc = coerce(proc, "fn")
#        except TypeError:
#            raise TypeError("illegal expression in functional position: %s" % proc)

        return proc(*(eval(x, env) for x in expr))


def eval(expr, env=current_env):
#    global current_module
    global current_env

    current_env = env
#
#    if not isinstance(env, environment_function):
#        current_module = env

    if isliteral(expr):
        return expr

    elif expr.type == "sym":
        try:
            return env[expr]
        except KeyError:
            raise NameError("%s is undefined" % expr)

    elif expr.type == "cons":
        return apply(eval(expr.car, env), expr.cdr)

    else:
        raise TypeError("unknown type %s" % expr)


def ssyntax_expand1(l, tokens, tree):
    item = r = None

    try:
        item = tokens.next()
        r = tokens.next()

        if l == "":
            func = ssyntax_rules[item]["prefix"]

            if r == "":
#                try:
#                    r = parse(tree.next(), tree)
#                except StopIteration:
#                    print "StopIteration!!!"
                r = ssyntax_expand1(r, tokens, tree)
            else:
                r = atom(r)

            return func(r)

        else:
            func = ssyntax_rules[item]["infix"]

            if r == "":
                r = ssyntax_expand1(r, tokens, tree)
            else:
                r = atom(r) #ssyntax_expand1(r, tokens, tree)

            return func(l, r)

    except KeyError as e:
        try:
            return atom(str(l) + item + r)
        except TypeError:
            print "-- keyerror", l, item, r
            return

    except StopIteration:
#        if l == "" or item is None:
        if item is None:
            raise

        elif l == "":
            func = ssyntax_rules[item]["prefix"]
            r = parse(tree.next(), tree)

            return func(r)

        else:
            try:
                return ssyntax_rules[item]["suffix"](l)
            except KeyError:
                return atom(l + item)


def num(token):
    try:
        return int_(int(token))
    except ValueError:
        try:
            return num_(float(token))
        except ValueError:
            return None


def atom(token):
    return num(token) or symbol(token)


def ssyntax_expand(s, tree):
    tokens = re.split(ssyntax_string, s)

    if len(tokens) == 1:
        return symbol(tokens[0])

    tokens = iter(tokens)
    item = tokens.next()

    try:
        while True:
            item = ssyntax_expand1(item, tokens, tree)
    except StopIteration:
        pass

    return item


class tokenize(object):
    def __init__(self, string):
#        return (s.replace('"', ' " ')
#                 .replace("'", " ' ")
#                 .replace('[', ' [ ')
#                 .replace(']', ' ] ')
#                 .replace("(", " ( ")
#                 .replace(")", " ) ").split())

        self.lineno = 0

        def f():
            result = []

            for x in string:
                result.append(x.value)

                if x.value == "\n":
                    self.currline = "".join(result)
                    yield self.currline
                    result = []

            if result:
                self.currline = "".join(result)
                yield self.currline

        f = f()

        def stream():
            for x in f:
                while True:
                    x = re.split(reader_syntax, x, 1)

                    if x[0] != "":
                        yield x[0]

                    try:
                        yield x[1]
                        x = x[2]
                    except IndexError:
                        break

        self.stream = stream()

    def __iter__(self):
        return self

    def next(self):
        return self.stream.next()


def macex(expr):
    while True:
        try:
            func = current_env[expr.car]

            if func.type == "mac":
                expr = func(*expr.cdr)
            else:
                raise NameError

        except (KeyError, NameError, AttributeError) as e:
            return expr


def macex1(expr):
    try:
        func = current_env[expr.car]

        if func.type == "mac":
            return func(*expr.cdr)
        else:
            raise NameError

    except (KeyError, NameError, AttributeError) as e:
        return expr


def has_spaces(token):
    return re.match(r"^\s$", token)


def parse(token, tokens):
#    global current_lineno
    global current_line

#    current_lineno = tokens.lineno
    current_line = tokens.currline

    if token == "(":
        token = tokens.next()

        if token == ")":
            return empty
        elif token == ".":
            raise SyntaxError("unexpected .")

        try:
            value = result = cons(parse(token, tokens), nil)

            while True:
                token = tokens.next()

                if token == ")":
                    break
                elif token == "]":
                    raise SyntaxError("( is not closed")
                elif token == ".":
                    vl = parse(tokens.next(), tokens)

                    value.cdr = vl

                    if vl.type == "sym":
                        token = tokens.next()

                        if token == ")":
                            break
                        else:
                            raise SyntaxError("expected ) but got %s" % parse(token, tokens))

                elif not has_spaces(token):
                    vl = parse(token, tokens)

                    if vl is not None:
                        value.cdr = cons(vl, nil)
                        value = value.cdr

        except StopIteration:
            if not option.loose_parens:
                raise SyntaxError("( is not closed")

        return result

    elif token == "[":
        item = result = cons(symbol("square-brackets"), nil)

        for token in tokens:
            if token == "]":
                break

            item.cdr = cons(parse(token, tokens), nil)
            item = item.cdr
        else:
            raise SyntaxError("[ is not closed")

        return result

    elif token == '"':
        result = []

        for token in tokens:
            if token == '"':
                break

            if token == "\\":
                value = next(tokens, None)

                if value is not None:
                    first = value[0]

                    try:
                        changed = string.mappings["\\%s" % first]

                        result.append(changed)
                        result.append(value[1:])

                    except KeyError:
                        raise SyntaxError("illegal escape sequence \\%s" % first)
#                        result.append(value)

            else:
                result.append(token)

        else: #* ending quote was not found
            raise SyntaxError('" is not closed')

        return string("".join(result))

    elif token == ";" or token == "#!":
        for token in tokens:
            if token == "\n":
                break

        return read_from(tokens) or nil

    elif token == "#\\":
        try:
            return char(tokens.next())
        except StopIteration:
            raise SyntaxError("cannot create an empty character with #\\")

    elif token == "|":
        value = []

        for token in tokens:
            if token == "|":
                break

            value.append(token)
        else:
            raise SyntaxError("| is not closed")

        return symbol("".join(value))

    elif token == ")":
        raise SyntaxError("unexpected )")

    elif has_spaces(token):
        return read_from(tokens)

    else:
        return num(token) or ssyntax_expand(token, tokens)


def read_from(tokens, eof=nil):
    try:
        return parse(tokens.next(), tokens)
    except StopIteration:
        return eof


#* used only for unit tests...
def readall(port):
    port = (char(x) for x in port)

    tokens = tokenize(port)

    for token in tokens:
        yield parse(token, tokens)


def sread(port, eof):
    return read_from(tokenize(port), eof)


def read(port):
    if isinstance(port, str):
        port = (char(x) for x in port)

    return read_from(tokenize(port))


def sref(item, index, value):
    set_name(index, value)

    try:
        if item.type == "table":
            if value == nil:
                del item[index]
            else:
                item[index] = value

        elif item.type == "cons" or item.type == "string":
            item[index] = value

        else:
            raise TypeError("can't call `sref` on expression %s" % item)

    except KeyError:
        pass

    return value


def err(*args):
    raise ArcError(" ".join([str(x) for x in args]))


def eq(x, y):
    if x.type == "cons":
        if x is y:
            return t
        else:
            return nil
    elif x == y:
        return t
    else:
        return nil


def coerce():
    def_radix = int_(10)

    def f(x, type, radix=def_radix):
        if x.type == type:
            return x

        if radix is not def_radix:
            try:
                if int(radix) != radix:
                    raise TypeError

            except TypeError:
                raise TypeError("radix must be an integer")


            if radix < 2 or radix > 16:
                raise ValueError("radix must be >= 2 and <= 16")

        if type == "fn":
            if x.type == "table":
                def f(n):
                    try:
                        return x[n]
                    except KeyError:
                        return nil

                return fnwrap(f)

            elif x.type == "cons" or x.type == "string":
                return fnwrap(lambda n: x[n])

        elif type == "string":
            if x.type == "char":
                return string(x.value)
            elif x.type == "int":
                return string(x.value)
            elif x.type == "num":
                return string(x.value)
            elif x.type == "cons":
                return string("".join(x.flatten()))
            elif x == nil:
                return string()
            elif x.type == "sym":
                return string(x.value)

        elif type == "cons":
            if x.type == "string":
                if x.empty():
                    return nil
                else:
                    return mapcons(x)

            elif x.type == "table":
                return mapcons(x, lambda n: cons(n, cons(x[n], nil)))

        elif type == "int":
            if x.type == "char":
    #            if radix.value != 10:
    #                raise ValueError("can't use a non-10 radix when converting from `char` to `int`")
                pass

            elif x.type == "num":
                if radix != 10:
                    raise ValueError("can't use a non-10 radix when converting from '%s to '%s" % (x.type, type))

                return int_(int(x))

            elif x.type == "string":
                try:
                    value = x.string()
                    return int_(int(value, int(radix)))
                except ValueError:
                    if radix == 10:
                        try:
                            return int_(int(float(value)))
                        except ValueError:
                            pass

        elif type == "num":
            if x.type == "int":
                if radix != 10:
                    raise ValueError("can't use a non-10 radix when converting from '%s to '%s" % (x.type, type))

                return num_(float(x))

            elif x.type == "string":
                try:
                    value = x.string()

                    if radix != 10:
                        value = int(value, int(radix))

                    return num_(float(value))

                except ValueError:
                    pass

        elif type == "char":
            if x.type == "string":
                return char(x.string())
            if x.type == "int":
                pass
            elif x.type == "num":
                pass
            elif x.type == "sym":
                return char(x.value)

        elif type == "sym":
            if x.type == "char":
                return symbol(x.value)
            elif x.type == "string":
                return symbol(x.string())

        elif type == "table":
            if x.type == "cons":
                try:
                    value = {}

                    for item in x:
                        if item.type == "cons":
                            v = item.cdr.car

                            if v != nil:
                                value[item.car] = v
                        else:
                            raise TypeError

                    return table(value)
                except TypeError:
                    pass

        raise TypeError("can't coerce %s to type '%s" % (x, type))

    return f

coerce = coerce()
#
#
#def reduce(f, args, sum=nil):
#    type = None
#
#    for x in args:
#        if type is None:
#            type = x.type
#        elif type != x.type:
#            if type == "int" and x.type == "num":
#                pass
#            elif type == "num" and x.type == "int":
#                continue
##            else:
##                pass
#
#            type = x.type
#
##        try:
#        sum = f(sum, coerce(x, type))
##        except TypeError:
##            pass
##            raise TypeError("cannot add types `%s` and `%s`" % (type, x.type))
#
#    return type, sum


def add(*args):
    value = []
    type = None

    for x in args:
        if type is None:
            prev = x

            if x == nil:
#                try:
#                    raise TypeError("can't add nil to %s" % args[1])
#                except IndexError:
#                    return nil
#
                type = symbol("cons")
                continue
            else:
                type = x.type

        if x == nil:
            continue

        elif type == "cons":
            try:
                x = coerce(x, type)
            except TypeError:
                raise TypeError("can't add %s and %s" % (prev, x))

            if x.type == "cons":
                for item in x:
                    value.append(item)

            elif x != nil:
                value.append(x)

        else:
            try:
                if type == "int":
                    if x.type == "num":
                        type = x.type
                    elif x.type == "string":
                        cx = x.string()

                        try:
                            value.append(int(cx))
                        except ValueError:
                            try:
                                value.append(float(cx))
                            except ValueError:
                                raise TypeError

                            type = symbol("num")

                        continue

                x = coerce(x, type)

            except TypeError:
                raise TypeError("can't add %s and %s" % (prev, x))

            if x.type == "int" or x.type == "num":
                value.append(x)
            elif x.type == "string":
                value.append(x.string())

        prev = x

    if type == "string":
        value = string("".join(value))

    elif type == "cons":
        value = mapcons(value)

        if value == nil:
            type = symbol("sym")

    elif type == "int" or type == "num":
        value = sum(value)

    return annotate(type, value)
#    return annotate(*reduce(lambda x, y: y + x.value, args))


def sub(*args):
    raise NotImplementedError("-")
#    return annotate(*reduce(lambda x, y: x - y.value, args))


def mul(*args):
    raise NotImplementedError("*")
#    return annotate(*reduce(lambda x, y: x * y.value, args))


def div(*args):
    raise NotImplementedError("/")
#    return annotate(*reduce(lambda x, y: x / y.value, args))


def mod(x, y):
    raise NotImplementedError("mod")
#    return ant("int", x.value % y.value)


def expt():
    raise NotImplementedError("expt")


def sqrt():
    raise NotImplementedError("sqrt")


def new_namespace(env=nil):
    if env == nil:
        env = safe_env

    return namespace(env)


def bound(x):
    try:
        current_env[x]
    except KeyError as e:
        return nil

    return t


def lt(*args):
    raise NotImplementedError("<")


def gt(*args):
    raise NotImplementedError(">")


def magic(*args):
    raise NotImplementedError("w/magic")


def square_brackets(x=None, *args):
    if x is None:
        return q_("fn", ("_",))
    elif x.type == "cons":
        return q_("fn", ("_",), x, *args)
    else:
        return q_("fn", ("_",), q_(x, *args))

#  (mac square-brackets (x . body)
#    (if (isa x 'cons) `(fn (_)  x ,@body)
#                      `(fn (_) (x ,@body))))


def instring(string):
    return stream(x for x in string)


def outstring():
    raise NotImplementedError("outstring")


#* not fully implemented...
def protect(before, after):
    try:
        val = before()
    finally:
        after()

    return val


def call_stdin(port, thunk):
    raise NotImplementedError("call-w/stdin")
    thunk()


def call_stdout(port, thunk):
    raise NotImplementedError("call-w/stdout")
    thunk()


def cli_args():
    cli = mapcons(args, lambda x: string(x))
    return lambda: cli

cli_args = cli_args()


def infile(path):
    if path.type != "string":
        raise TypeError("expected a string")

#    path = os.path.join(os.path.dirname(args[0]), path.string())
#    path = os.path.join(sys.path[0], path.string())
    path = path.string()

    file = open(path, "r")

    def f():
        for x in file:
            for x in x:
                yield char(x)

    f = f()

    return stream(f, path=path)


def close(x):
#    print "closing %s" % x
    x.close()
    return nil


#* not implemented
def atomic_invoke(f):
    return f()


def readc(input=nil):
    if input == nil:
        input = current_env["stdin"]

    return coerce(input, "fn")()


def writec(x, out=nil):
    if out == nil:
        out = current_env["stdout"]

    return coerce(out, "fn")(coerce(x, "char"))


def maptable(f, t):
    for x, y in t.value.items():
        f(x, y)

    return t


def car(x):
    try:
        return x.car
    except AttributeError:
        raise ArcError("can't use `car` on the expression %s" % x)


def cdr(x):
    try:
        return x.cdr
    except AttributeError:
        raise ArcError("can't use `cdr` on the expression %s" % x)


def newstring(n, c):
    return string(coerce(c, "char").value * coerce(n, "int").value)


safe_env.update({
                     ### Symbols

                "nil" : nil,
                  "t" : t,


                     ### Noop functions, but they're needed by arc.arc

            "ssyntax" : fnwrap(lambda x: nil, "ssyntax"),


                     ### Fexprs

         "quasiquote" : fexpr(lambda x: quasiquote(x, current_env), "quasiquote"),
#              "apply" : fexpr(lambda fn, *args: apply(eval(fn, current_env), flat1(args)), "apply"),
                 "if" : fexpr(if_, "if"),
#         "quasiquote" : fexpr(quasiquote),
             "assign" : fexpr(assign),
              "quote" : fexpr(quote),
                 "fn" : fexpr(fn),


                     ### Functions needed by arc.arc

            "seconds" : fnwrap(lambda: int_(int(time.time())), "seconds"),
              "apply" : fnwrap(lambda fn, *args: apply(eval(fn, current_env), flat1(args)), "apply"),
#              "apply" : fnwrap(lambda fn, *args: apply(fn, args), "apply"),
              "table" : fnwrap(lambda: table(), "table"),
               "type" : fnwrap(lambda x: x.type, "type"),
                "len" : fnwrap(lambda x: int_(len(x)), "len"),
#           "ssexpand" : fnwrap(ssyntax_expand, "ssexpand"),
                 "is" : fnwrap(eq, "is"),
                  "<" : fnwrap(lt, "<"),
                  ">" : fnwrap(gt, ">"),
      "atomic-invoke" : fnwrap(atomic_invoke),
      "call-w/stdout" : fnwrap(call_stdout),
       "call-w/stdin" : fnwrap(call_stdin),
          "newstring" : fnwrap(newstring),
          "outstring" : fnwrap(outstring),
           "annotate" : fnwrap(annotate),
           "instring" : fnwrap(instring),
           "maptable" : fnwrap(maptable),
            "protect" : fnwrap(protect),
             "coerce" : fnwrap(coerce),
             "macex1" : fnwrap(macex1),
             "writec" : fnwrap(writec),
#              "apply" : fnwrap(apply),
              "bound" : fnwrap(bound),
              "close" : fnwrap(close),
              "macex" : fnwrap(macex),
              "readc" : fnwrap(readc),
              "sread" : fnwrap(sread),
#              "table" : fnwrap(table),
               "cons" : fnwrap(cons),
               "eval" : fnwrap(eval),
               "disp" : fnwrap(disp),
               "sref" : fnwrap(sref),
               "uniq" : fnwrap(uniq),
                "car" : fnwrap(car),
                "cdr" : fnwrap(cdr),
#                "ccc" : fnwrap(ccc),
                "err" : fnwrap(err),


                     ### Math stuff used in arc.arc

                  "+" : fnwrap(add, "+"),
                  "-" : fnwrap(sub, "-"),
                  "*" : fnwrap(mul, "*"),
                  "/" : fnwrap(div, "/"),
                "mod" : fnwrap(mod),


                     ### Stuff not used in arc.arc, but defined in ac.scm

                "rep" : fnwrap(lambda x: x.rep, "rep"),
               "expt" : fnwrap(expt),
               "sqrt" : fnwrap(sqrt),


                     ### Other

                "sig" : table(),


                     ### Implementation-specific stuff

    "square-brackets" : macro(square_brackets, "square-brackets"),

        "current-env" : fnwrap(lambda: current_env, "current-env"),
      "new-namespace" : fnwrap(new_namespace, "new-namespace"),
            "w/magic" : fnwrap(magic, "w/magic"),
               "attr" : fnwrap(attr),

    "gensym-counter*" : int_(random.randrange(100, 999)),
})


global_env.update({
#            "outfile" : fnwrap(outfile),
#             "inside" : fnwrap(inside),
             "infile" : fnwrap(infile),
             "stderr" : fnwrap(stderr),
             "stdout" : fnwrap(stdout),
              "stdin" : fnwrap(stdin),


                     ### Implementation-specific stuff

       "__built-ins*" : global_env,
           "cli-args" : fnwrap(cli_args),
              "yield" : fnwrap(yield_),
})

if option.backwards_compat:
    global_env.update({
             "stderr" : fnwrap(lambda: stderr, "stderr"),
             "stdout" : fnwrap(lambda: stdout, "stdout"),
              "stdin" : fnwrap(lambda: stdin, "stdin"),
    })


def print_error(e, out=sys.stdout):
    print >>out, "- %s in \"%s\" on line %s:\n    %s" % (type(e).__name__, current_file, current_lineno, e)


def print_stack(e, limit=6, out=sys.stdout):
    length = len(stack)

    if length:
        print >>out, "\n- Stacktrace:"

        if length > limit:
            limit = limit / 2

            for item in stack[0:limit]:
                print >>out, "    %s" % item

            print >>out, "    ... %s omitted ..." % (length - (limit * 2))

            for item in stack[-limit:]:
                print >>out, "    %s" % item

        else:
            for item in stack:
                print >>out, "    %s" % item


def load(path, env=current_env):
    f = infile(string(path))

    try:
        x = None

        while True:
            try:
                del stack[:]

                x = read(f)
#                x = macex(x)
                y = eval(x, env)
            except Exception as e:
#                raise
                print "- Expression:\n    %s\n" % current_line#x

                print_error(e)
                print_stack(e)

                print
                return

            if x == nil:
                return

    finally:
        f.close()


class std_capture(object):
    def __init__(self):
        self.value = []

    def write(self, x):
        self.value.append(x)


tests = {
    "failed_num": 0,
    "failed": {}
}

def tester(path):
    global tests

    abs_path = os.path.join(sys.path[0], path)

    os.chdir(os.path.dirname(abs_path))

    (stdout, stderr) = (sys.stdout, sys.stderr)

    env = new_namespace(global_env)

    failed = False

    with open(abs_path, "r") as file:
        s = file.read()

        s = re.findall(re.compile(r"^>>> (.+\n)((?:^\.{3}.*\n)*)((?:^(?!\n).*\n)*)", re.M), s) # (?!\.{3})((?:\n(?!\n)|[^\n]+)*)

#        s = re.sub(re.compile(r"^>>>(.)", re.M), r'>>>\1eval(read("""', s)
#        s = re.sub(re.compile(r"^(>>>.+|\.{3}.+)\n(?!\.{3})", re.M), r'\1"""))\n', s)

        def f(s):
            c = s.group(1)

            if c == " ":
                return ""
            elif c == "\n":
                return c
            else:
                raise SyntaxError("expected a space or newline after ...")


        def indent(s, spaces=4):
            return "\n".join([" " * spaces + x for x in s.split("\n")])

        for (l, r, expect) in s:
            exception = None
            del stack[:]

            try:
                r = re.sub(re.compile(r"^\.{3}([\s\S])", re.M), f, r)
                l = l + r
            except SyntaxError as e:
                failed = True

                print >>stdout, "\n- SyntaxError in \"%s\":\n    %s\n\n>>> %s%s   ^" % (path, e, l, r)
                print >>stdout, "\n%s" % ("=" * 80)
                continue

            expect = re.sub(re.compile(r"^\\\n", re.M), "\n", expect.rstrip("\n"))
            sys.stdout = sys.stderr = std_capture()

            try:
                sys.stdout.write(repr(eval(read(l), env)))
            except Exception as e:
                exception = e
                sys.stdout.write("error: %s" % e)

            result = "".join(sys.stdout.value)

            if result != expect:
                failed = True

                tests["failed_num"] += 1

                try:
                    tests["failed"][path] += 1
                except KeyError as e:
                    tests["failed"][path] = 1

                print >>stdout, "\n- Failed example in \"%s\":\n%s" % (path, indent(l)) # at line #

                print >>stdout, "- Expected:\n%s\n" % indent(expect)

                if exception:
                    print >>stdout, "- Got:\n    error: %s" % exception

                    print_stack(exception, limit=10, out=stdout)

                else:
                    print >>stdout, "- Got:\n%s" % indent(result)

                print >>stdout, "\n%s" % ("=" * 80)


    if failed:
        print >>stdout, "%s" % ("=" * 80)

    (sys.stdout, sys.stderr) = (stdout, stderr)


done_loading = None

def repl(prompt=">>> "):
    global done_loading

    def evaler(value):
        try:
            print eval(read(char(x) for x in value))
            print
        except Exception as e:
            print "- Expression:\n    %s\n" % current_line, #x

            print_error(e)
            print_stack(e)

            print
#            raise
#            print "error:", e
#            print

    while True:
        try:
            value = raw_input(prompt)

            if value:
                if done_loading:
                    evaler(value);
                else:
                    def f():
                        evaler(value)
                        repl(prompt)

                    done_loading = f
                    break
#
#        except KeyboardInterrupt as e:
#            print "^C"

        except (EOFError, KeyboardInterrupt):
            print
            sys.exit()


if __name__ == "__main__":
    if option.repl or (not args and not option.run_tests):
        repl()

    old = option.backwards_compat
    option.backwards_compat = False

    load(os.path.join(sys.path[0], "lib/arc.arc"), global_env)

    if option.run_tests:
        option.backwards_compat = False

        import doctest
        doctest.testfile("tests/arc.py.tests", optionflags=doctest.ELLIPSIS)
#        doctest.testfile("tests/+", optionflags=doctest.ELLIPSIS)
        doctest.testfile("tests/sref", optionflags=doctest.ELLIPSIS)
#        doctest.testfile("tests/modules", optionflags=doctest.ELLIPSIS)
        doctest.testfile("tests/uniq", optionflags=doctest.ELLIPSIS)

        tester("tests/arc")
        tester("tests/err")
        tester("tests/attr")
        tester("tests/apply")
#        tester("tests/iter")
        tester("tests/macros")
#        tester("tests/macex")
#        tester("tests/yield")
#        tester("tests/coerce")
#        tester("tests/ssyntax")
        tester("tests/destructuring")
#        tester("tests/sweet-expressions")

        option.backwards_compat = True
        tester("tests/backwards-compat")

        if tests["failed_num"] != 0:
            print "\n- %s tests failed:" % tests["failed_num"]

            for (n, v) in sorted(tests["failed"].items(), key=lambda x: x[1], reverse=True):
                print '    {0:>2} in "{2}"'.format(v, " ", n)

            print
            print "=" * 80

    option.backwards_compat = old

    if args:
        os.chdir(os.path.dirname(args[0]))
        load(args[0], new_namespace(global_env))

    if done_loading:
        done_loading()
